﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Eptalon.TileEngine;
using Eptalon.GraphicsEngine;
using Eptalon.SceneManagement;
using Eptalon.Entities;
using EptalonContentImporter;
using EptalonContentImporter.Types;

namespace BallsOfDoom
{
    public class AnimatedSprite : Sprite
    {
        #region Fields
        Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>> animations;

        Dictionary<string, Dictionary<AnimationKey, Animation>> additionalAnimations;
        Dictionary<string, Texture2D> additionalTextures;

        List<string> playOnceStack;

        MovementState currentMovement;
        AttackState currentAttack;
        AnimationKey currentDirection;

        bool isAnimating;
        Dictionary<MovementState, Dictionary<AttackState,Texture2D>> textures;
        #endregion

        #region Properties
        public int DefaultWidth;
        public int DefaultHeight;

        public Dictionary<MovementState, Dictionary<AttackState, Texture2D>> Textures
        {
            get { return textures; }
        }

        public Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>> Animations
        {
            get { return animations; }
        }

        public bool PlaysAdditionalAnimation { get { return playOnceStack.Count > 0; } }

        public Animation CurrentAnimation
        {
            get 
            {
                if (PlaysAdditionalAnimation)
                    return additionalAnimations[playOnceStack[playOnceStack.Count-1]][CurrentDirection];
                else
                    return animations[CurrentMovement][CurrentAttack][CurrentDirection]; 
            }
        }

        public string CurrentAnimationName
        {
            get
            {
                if (PlaysAdditionalAnimation)
                    return playOnceStack[playOnceStack.Count - 1];
                else
                    return "";
            }
        }

        public MovementState CurrentMovement
        {
            get { return currentMovement; }
            set { currentMovement = value; }
        }

        public AttackState CurrentAttack
        {
            get { return currentAttack; }
            set { currentAttack = value; }
        }

        public AnimationKey CurrentDirection
        {
            get { return currentDirection; }
            set { currentDirection = value; }
        }

        public Texture2D CurrentTexture
        {
            get { return textures[currentMovement][currentAttack]; }
        }

        public bool IsAnimating
        {
            get { return isAnimating; }
            set { isAnimating = value; }
        }
        
        public new int Width
        {
            get { return animations[currentMovement][currentAttack][currentDirection].FrameWidth; }
        }
        public new int Height
        {
            get { return animations[currentMovement][currentAttack][currentDirection].FrameHeight; }
        }
#endregion

        #region Constructors

        public AnimatedSprite()
        {
            textures = new Dictionary<MovementState, Dictionary<AttackState, Texture2D>>();
            animations = new Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>>();
            additionalAnimations = new Dictionary<string, Dictionary<AnimationKey, Animation>>();
            additionalTextures = new Dictionary<string, Texture2D>();
            playOnceStack = new List<string>();

            currentMovement = MovementState.Resting;
            currentAttack = AttackState.None;
            currentDirection = AnimationKey.Down;
            Perspective = true;
            IsAnimating = true;

            DefaultHeight = 0;
            DefaultWidth = 0;
        }

        public AnimatedSprite(string _fileName, Game1 _gameRef)
        {
            textures = new Dictionary<MovementState, Dictionary<AttackState, Texture2D>>();
            animations = new Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>>();
            additionalAnimations = new Dictionary<string, Dictionary<AnimationKey, Animation>>();
            additionalTextures = new Dictionary<string, Texture2D>();
            playOnceStack = new List<string>();

            currentMovement = MovementState.Resting;
            currentAttack = AttackState.None;
            currentDirection = AnimationKey.Down;
            Perspective = true;
            IsAnimating = true;

            

            AnimatedSpriteData data = _gameRef.Content.Load<AnimatedSpriteData>(_fileName);

            foreach (MovementState movement in data.Animations.Keys)
            {
                animations[movement] = new Dictionary<AttackState, Dictionary<AnimationKey, Animation>>();
                textures[movement] = new Dictionary<AttackState, Texture2D>();
                currentMovement = movement;
                foreach (AttackState attack in data.Animations[movement].Keys)
                {
                    currentAttack = attack;
                    textures[movement][attack] = Graphics.Instance.GetTexture(data.Animations[movement][attack][AnimationKey.Down].SpriteSheetName);
                    animations[movement][attack] = new Dictionary<AnimationKey, Animation>();
                    DefaultHeight = (int)data.SpriteSheetInfos[movement][attack].FrameDimensions.X;
                    DefaultWidth = (int)data.SpriteSheetInfos[movement][attack].FrameDimensions.Y;
                    int i = 0;
                    foreach (AnimationKey direction in data.Animations[movement][attack].Keys)
                    {
                        currentDirection = direction;
                        int itemsPerRow = textures[movement][attack].Width / (int)data.SpriteSheetInfos[movement][attack].FrameDimensions.X;
                        int x = (int)(i * DefaultHeight * data.Animations[movement][attack][direction].Frames.Length) % textures[movement][attack].Width;
                        int y = (int)(i * DefaultHeight * data.Animations[movement][attack][direction].Frames.Length) / textures[movement][attack].Width;
                        animations[movement][attack][direction] = new Animation(data.Animations[movement][attack][direction].Frames.Length,
                                                                                (int)data.SpriteSheetInfos[movement][attack].FrameDimensions.X,
                                                                                (int)data.SpriteSheetInfos[movement][attack].FrameDimensions.Y,
                                                                                x,
                                                                                y,
                                                                                data.Animations[movement][attack][direction].DrawingOffset);
                        i++;
                        for (int j = 0; j < data.Animations[movement][attack][direction].Frames.Length; j++)
                        {
                            animations[movement][attack][direction].FPS[j] = data.Animations[movement][attack][direction].Frames[j].FPS;
                            animations[movement][attack][direction].Offsets[j] = data.Animations[movement][attack][direction].Frames[j].Offset;
                        }
                       
                    }
                }
            }
        }

        public AnimatedSprite(int _defaultWidth, int _defaultHeight)
        {
            textures = new Dictionary<MovementState, Dictionary<AttackState, Texture2D>>();
            animations = new Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>>();
            additionalAnimations = new Dictionary<string, Dictionary<AnimationKey, Animation>>();
            additionalTextures = new Dictionary<string, Texture2D>();
            playOnceStack = new List<string>();

            currentMovement = MovementState.Resting;
            currentAttack = AttackState.None;
            currentDirection = AnimationKey.Down;
            Perspective = true;
            IsAnimating = true;

            DefaultHeight = _defaultHeight;
            DefaultWidth = _defaultWidth;
        }

        public AnimatedSprite(Dictionary<MovementState, Dictionary<AttackState, Texture2D>> _textures, Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>> _animations)
            : base(_textures[MovementState.Resting][AttackState.None])
        {
            textures = _textures;
            animations = new Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>>();
            foreach (MovementState name in _animations.Keys)
            {
                animations[name] = new Dictionary<AttackState, Dictionary<AnimationKey, Animation>>();
                foreach (AttackState attack in _animations[name].Keys)
                {
                    animations[name][attack] = new Dictionary<AnimationKey, Animation>();
                    foreach (AnimationKey key in _animations[name][attack].Keys)
                    {
                        animations[name][attack].Add(key, (Animation)_animations[name][attack][key].Clone());
                    }
                }
            }

            additionalAnimations = new Dictionary<string, Dictionary<AnimationKey, Animation>>();
            additionalTextures = new Dictionary<string, Texture2D>();
            playOnceStack = new List<string>();

            currentMovement = MovementState.Resting;
            currentAttack = AttackState.None;
            currentDirection = AnimationKey.Down;
            SourceRectangle = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect;
            Perspective = true;
            IsAnimating = true;

            DefaultWidth = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect.Width;
            DefaultHeight = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect.Height;
        }

        public AnimatedSprite(Dictionary<MovementState, Dictionary<AttackState, Texture2D>> _textures, Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>> _animations,
            Dictionary<string, Dictionary<AnimationKey, Animation>> _additionalAnimations, Dictionary<string, Texture2D> _additionalTextures)
            : base(_textures[MovementState.Resting][AttackState.None])
        {
            textures = _textures;
            animations = new Dictionary<MovementState, Dictionary<AttackState, Dictionary<AnimationKey, Animation>>>();
            foreach (MovementState name in _animations.Keys)
            {
                animations[name] = new Dictionary<AttackState, Dictionary<AnimationKey, Animation>>();
                foreach (AttackState attack in _animations[name].Keys)
                {
                    animations[name][attack] = new Dictionary<AnimationKey, Animation>();
                    foreach (AnimationKey key in _animations[name][attack].Keys)
                    {
                        animations[name][attack].Add(key, (Animation)_animations[name][attack][key].Clone());
                    }
                }
            }

            additionalTextures = _additionalTextures;
            additionalAnimations = new Dictionary<string, Dictionary<AnimationKey, Animation>>();
            foreach (string name in _additionalAnimations.Keys)
            {
                additionalAnimations[name] = new Dictionary<AnimationKey, Animation>();
                foreach (AnimationKey key in _additionalAnimations[name].Keys)
                {
                    additionalAnimations[name][key] = (Animation)_additionalAnimations[name][key].Clone();
                }
            }
           
            playOnceStack = new List<string>();

            currentMovement = MovementState.Resting;
            currentAttack = AttackState.None;
            currentDirection = AnimationKey.Down;
            SourceRectangle = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect;
            Perspective = true;
            IsAnimating = true;

            DefaultWidth = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect.Width;
            DefaultHeight = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect.Height;
        }

        #endregion

        #region Load from File
        public void LoadAnimationFromFile(string _filename, MovementState _moveState, AttackState _attackState, int _frameWidth, int _frameHeight, int _frameCount, Vector2 _drawingOffset, int _fps = 10, bool _isPlayOnce = false)
        {
            if (!textures.ContainsKey(_moveState))
                textures[_moveState] = new Dictionary<AttackState, Texture2D>();

            textures[_moveState][_attackState] = Graphics.Instance.GetTexture(_filename);

            if (!animations.ContainsKey(_moveState))
                animations[_moveState] = new Dictionary<AttackState, Dictionary<AnimationKey, Animation>>();

            if (!animations[_moveState].ContainsKey(_attackState))
                animations[_moveState][_attackState] = new Dictionary<AnimationKey, Animation>();

            int xOffset = 0, yOffset = 0;
            if (_moveState == MovementState.Resting && _attackState == AttackState.None)
            {
                xOffset = 1;
                yOffset = 2;
            }

            animations[_moveState][_attackState].Add(AnimationKey.Up, new Animation(_frameCount, _frameWidth, _frameHeight, xOffset, yOffset, _drawingOffset, _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Down, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * _frameCount) % textures[_moveState][_attackState].Width + xOffset * 2,
                       (_frameWidth * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight + yOffset,
                       _drawingOffset, _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Left, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 2 * _frameCount) % textures[_moveState][_attackState].Width + xOffset * 3 + (xOffset > 0 ? 2 : 0),
                       (_frameWidth * 2 * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight + yOffset,
                       _drawingOffset, _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Right, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 3 * _frameCount) % textures[_moveState][_attackState].Width + xOffset * 4 + (xOffset > 0 ? 3 : 0),
                       (_frameWidth * 3 * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight + yOffset,
                       _drawingOffset, _isPlayOnce, _fps, textures[_moveState][_attackState].Width));
        }

        public void LoadAnimationFromFile(string _filename, MovementState _moveState, AttackState _attackState, int _frameWidth, int _frameHeight, int _frameCount, Vector2[] _drawingOffsets, int _fps = 10, bool _isPlayOnce = false)
        {
            if (!textures.ContainsKey(_moveState))
                textures[_moveState] = new Dictionary<AttackState, Texture2D>();

            textures[_moveState][_attackState] = Graphics.Instance.GetTexture(_filename);

            if (!animations.ContainsKey(_moveState))
                animations[_moveState] = new Dictionary<AttackState, Dictionary<AnimationKey, Animation>>();

            if (!animations[_moveState].ContainsKey(_attackState))
                animations[_moveState][_attackState] = new Dictionary<AnimationKey, Animation>();

            animations[_moveState][_attackState].Add(AnimationKey.Up, new Animation(_frameCount, _frameWidth, _frameHeight, 0, 0, _drawingOffsets[0], _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Down, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * _frameCount) % textures[_moveState][_attackState].Width,
                       (_frameWidth * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight,
                       _drawingOffsets[1], _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Left, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 2 * _frameCount) % textures[_moveState][_attackState].Width,
                       (_frameWidth * 2 * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight,
                       _drawingOffsets[2], _isPlayOnce, _fps, textures[_moveState][_attackState].Width));

            animations[_moveState][_attackState].Add(AnimationKey.Right, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 3 * _frameCount) % textures[_moveState][_attackState].Width,
                       (_frameWidth * 3 * _frameCount) / textures[_moveState][_attackState].Width * _frameHeight,
                       _drawingOffsets[3], _isPlayOnce, _fps, textures[_moveState][_attackState].Width));
        }

        public void LoadAdditionalAnimationFromFile(string _name, string _filename, int _frameWidth, int _frameHeight, int _frameCount, Vector2[] _drawingOffset, int _fps = 10, bool _isPlayOnce = false)
        {
            additionalTextures[_name] = Graphics.Instance.GetTexture(_filename);
            additionalAnimations[_name] = new Dictionary<AnimationKey, Animation>();

            additionalAnimations[_name].Add(AnimationKey.Up, new Animation(_frameCount, _frameWidth, _frameHeight, 0, 0, _drawingOffset[0], _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Down, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset[1], _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Left, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 2 * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * 2 * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset[2], _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Right, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 3 * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * 3 * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset[3], _isPlayOnce, _fps, additionalTextures[_name].Width));
        }

        public void LoadAdditionalAnimationFromFile(string _name, string _filename, int _frameWidth, int _frameHeight, int _frameCount, Vector2 _drawingOffset, int _fps = 10, bool _isPlayOnce = false)
        {
            additionalTextures[_name] = Graphics.Instance.GetTexture(_filename);
            additionalAnimations[_name] = new Dictionary<AnimationKey, Animation>();

            additionalAnimations[_name].Add(AnimationKey.Up, new Animation(_frameCount, _frameWidth, _frameHeight, 0, 0, _drawingOffset, _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Down, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset, _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Left, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 2 * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * 2 * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset, _isPlayOnce, _fps, additionalTextures[_name].Width));

            additionalAnimations[_name].Add(AnimationKey.Right, new Animation(_frameCount, _frameWidth, _frameHeight,
                       (_frameWidth * 3 * _frameCount) % additionalTextures[_name].Width,
                       (_frameWidth * 3 * _frameCount) / additionalTextures[_name].Width * _frameHeight,
                       _drawingOffset, _isPlayOnce, _fps, additionalTextures[_name].Width));
        }

        public void LoadAdditionalAnimationFromFile(string _name, string _filename, AnimationKey _key, int _frameWidth, int _frameHeight, int _frameCount, Vector2 _drawingOffset, int _fps = 10, bool _isPlayOnce = false)
        {
            additionalTextures[_name] = Graphics.Instance.GetTexture(_filename);
            additionalAnimations[_name] = new Dictionary<AnimationKey, Animation>();

            additionalAnimations[_name].Add(_key, new Animation(_frameCount, _frameWidth, _frameHeight, 0, 0, _drawingOffset, _isPlayOnce, _fps, additionalTextures[_name].Width));
        }
        #endregion

        public override void OnAdd(GameObject _gameObject)
        {
            base.OnAdd(_gameObject);
        }

        public override void OnRemove()
        {
            base.OnRemove();
        }

        public override void Update(GameTime gameTime)
        {
            if (isAnimating)
            {
                if(PlaysAdditionalAnimation)
                {
                    string currentAnim = playOnceStack[playOnceStack.Count-1];
                    Dictionary<AnimationKey, Animation> currentStackAnimation = additionalAnimations[currentAnim];
                    AnimationKey k = AnimationKey.Down;
                    foreach (AnimationKey a in currentStackAnimation.Keys)
                    {
                        currentStackAnimation[a].Update(gameTime);
                        k = a;
                    }
                    if (currentStackAnimation[k].IsFinished)
                        playOnceStack.RemoveAt(playOnceStack.Count - 1);

                    texture = additionalTextures[currentAnim];
                    SourceRectangle = additionalAnimations[currentAnim][CurrentDirection].CurrentFrameRect;
                    drawingOffset = additionalAnimations[currentAnim][CurrentDirection].DrawingOffset;
                }
                else
                {
                    foreach (AnimationKey a in animations[currentMovement][currentAttack].Keys)
                    {
                        animations[currentMovement][currentAttack][a].Update(gameTime);
                    }
                    texture = textures[CurrentMovement][currentAttack];
                    SourceRectangle = animations[currentMovement][currentAttack][currentDirection].CurrentFrameRect;
                    drawingOffset = animations[currentMovement][currentAttack][currentDirection].DrawingOffset;
                }
            }
        }

        #region Public Methods
        public void Reset()
        {
            foreach (AnimationKey mov in Enum.GetValues(typeof(AnimationKey)))
                animations[currentMovement][currentAttack][mov].Reset();
        }

        public void SetAnimation(MovementState _state)
        {
            currentMovement = _state;
            texture = textures[currentMovement][currentAttack];
            Reset();
        }

        public void SetAnimation(MovementState _state, AttackState _attackState)
        {
            currentAttack = _attackState;
            SetAnimation(_state);
        }

        public void PlayOnce(string _name, bool _append = false)
        {
            if (additionalAnimations.ContainsKey(_name))
            {
                foreach (AnimationKey a in additionalAnimations[_name].Keys)
                    additionalAnimations[_name][a].Reset();

                if (_append)
                    playOnceStack.Insert(0, _name);
                else
                    playOnceStack.Add(_name);
            }
            else
            {
                Console.WriteLine("Additional Animation: " + _name + " not found");
            }
        }

        public void StopAdditionalAnimation()
        {
            if(PlaysAdditionalAnimation)
                playOnceStack.RemoveAt(playOnceStack.Count-1);
        }
       
        public object Clone()
        {
            AnimatedSprite sprite = new AnimatedSprite(textures, animations);
            return sprite;
        }

        #endregion
    }
}